<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
  <script src="wct-browser-config.js"></script>
  <script src="../../node_modules/wct-browser-legacy/browser.js"></script>
  <script type="module" src="../../polymer-legacy.js"></script>
</head>
<body>
  <test-fixture id="basic">
    <template>
      <x-basic></x-basic>
    </template>
  </test-fixture>

  <test-fixture id="another">
    <template>
      <x-basic></x-basic>
    </template>
  </test-fixture>

<script type="module">
import { Polymer } from '../../polymer-legacy.js';
import { Debouncer } from '../../lib/utils/debounce.js';
import { microTask, timeOut, animationFrame, idlePeriod } from '../../lib/utils/async.js';
import { enqueueDebouncer, flush } from '../../lib/utils/flush.js';
Polymer({is: 'x-basic'});

suite('enqueueDebouncer & flush', function() {

  // NOTE: This is a regression test; the bug it fixed only occurred if the
  // debouncer was flushed before any microtasks run, hence it should be
  // first in this file
  test('re-enqueue canceled debouncer', function() {
    const cb = sinon.spy();
    let db;
    db = Debouncer.debounce(null, microTask, cb);
    enqueueDebouncer(db);
    db.cancel();
    assert.equal(db.isActive(), false);
    assert.equal(cb.callCount, 0);
    db = Debouncer.debounce(db, microTask, cb);
    enqueueDebouncer(db);
    flush();
    assert.isTrue(cb.calledOnce);
  });

  test('flushDebouncers from enqueued debouncer', function(done) {
    const cb = sinon.spy(() => flush());
    let db = Debouncer.debounce(null, microTask, cb);
    enqueueDebouncer(db);
    setTimeout(() => {
      assert.isTrue(cb.calledOnce);
      done();
    });
  });

  const testEnqueue = (shouldFlush, done) => {
    const actualOrder = [];
    const enqueue = (type, {db, cb} = {}) => {
      cb = cb || (() => actualOrder.push(cb));
      db = Debouncer.debounce(db, type, cb);
      enqueueDebouncer(db);
      return {db, cb};
    };
    const db1 = enqueue(microTask);
    const db2 = enqueue(microTask);
    const db3 = enqueue(timeOut);
    const db4 = enqueue(microTask);
    enqueue(microTask, db2);
    enqueue(microTask, db1);
    if (shouldFlush) {
      flush();
      assert.deepEqual(actualOrder, [db1.cb, db2.cb, db3.cb, db4.cb]);
      done();
    } else {
      timeOut.run(() => {
        assert.deepEqual(actualOrder, [db4.cb, db2.cb, db1.cb, db3.cb]);
        done();
      });
    }
  };

  test('non-flushed', function(done) {
    testEnqueue(false, done);
  });

  test('flushed', function(done) {
    testEnqueue(true, done);
  });

  test('reentrant flush', function() {
    const cb2 = sinon.spy();
    let db2;
    const cb1 = sinon.spy(() => {
      flush();
      db2 = Debouncer.debounce(null, microTask, cb2);
      enqueueDebouncer(db2);
    });
    const db1 = Debouncer.debounce(null, microTask, cb1);
    enqueueDebouncer(db1);
    flush();
    assert.isTrue(cb1.calledOnce);
    assert.isTrue(cb2.calledOnce);
  });

});

suite('debounce', function() {
  var element;

  setup(function() {
    element = fixture('basic');
  });

  test('debounce (no-wait)', function(done) {
    var called = 0;
    var callback = function() {
      called++;
    };

    element.debounce('job', callback);
    element.debounce('job', callback);
    element.debounce('job', callback);

    setTimeout(function() {
      assert.equal(called, 1, 'debounce should be called exactly once');
      done();
    }, 50);
  });

  test('debounce (wait)', function(done) {
    var called = 0;
    var now = Date.now();
    var callback = function() {
      called++;
    };

    element.debounce('job', callback);
    element.debounce('job', callback, 100);
    element.debounce('job', callback, 100);

    setTimeout(function() {
      assert.equal(called, 1, 'debounce should be called exactly once');
      assert(Date.now() - now > 100, 'debounce should be called after at least 100ms');
      done();
    }, 200);
  });

  suite('debounce does not carry across multiple elements', function() {
    var element1, element2;

    setup(function() {
      element1 = fixture('basic');
      element2 = fixture('another');
    });

    test('debounce (no-wait)', function(done) {
      var called = 0;
      var callback = function() {
        called++;
      };

      element1.debounce('job', callback);
      element2.debounce('job', callback);
      element1.debounce('job', callback);
      element2.debounce('job', callback);

      setTimeout(function() {
        assert.equal(called, 2, 'debounce should be called exactly twice');
        done();
      }, 50);
    });
  });

  suite('Polymer.Debouncer.debounce', function() {
    test('same debouncer and multiple micro tasks', function(done) {
      var callback = sinon.spy();
      var job = Debouncer.debounce(null, microTask, callback);
      Debouncer.debounce(job, microTask, callback);

      setTimeout(function() {
        assert.isTrue(callback.calledOnce, 'callback should be called once');
        done();
      });
    });

    test('flush debouncer', function(done) {
      var callback = sinon.spy();
      var job = Debouncer.debounce(null, microTask, callback);

      setTimeout(function() {
        job.flush();
        assert.isTrue(callback.calledOnce, 'callback should be called once');
        done();
      });
    });

    test('different debouncers and multiple micro tasks', function(done) {
      var callback = sinon.spy();
      var job1 = Debouncer.debounce(null, microTask, callback);
      Debouncer.debounce(job1, microTask, callback);
      var job2 = Debouncer.debounce(null, microTask, callback);
      Debouncer.debounce(job2, microTask, callback);

      setTimeout(function() {
        assert.isTrue(callback.calledTwice, 'callback should be called twice');
        done();
      });
    });

    test('same debouncer and multiple timers', function(done) {
      var callback = sinon.spy();
      var job = Debouncer.debounce(null, timeOut.after(10), callback);
      Debouncer.debounce(job, timeOut.after(10), callback);

      setTimeout(function() {
        assert.isTrue(callback.calledOnce, 'callback should be called once');
        done();
      }, 20);
    });

    test('different debouncers and multiple timers', function(done) {
      var callback = sinon.spy();
      var job1 = Debouncer.debounce(null, timeOut.after(10), callback);
      Debouncer.debounce(job1, timeOut.after(10), callback);
      var job2 = Debouncer.debounce(null, timeOut.after(10), callback);
      Debouncer.debounce(job2, timeOut.after(10), callback);

      setTimeout(function() {
        assert.isTrue(callback.calledTwice, 'callback should be called twice');
        done();
      }, 20);
    });

    test('same debouncer and multiple animation frames', function(done) {
      var callback = sinon.spy();
      var job = Debouncer.debounce(null, animationFrame, callback);
      Debouncer.debounce(job, animationFrame, callback);

      requestAnimationFrame(function() {
        assert.isTrue(callback.calledOnce, 'callback should be called once');
        done();
      });
    });

    test('different debouncer and multiple animation frames', function(done) {
      var callback = sinon.spy();
      var job1 = Debouncer.debounce(null, animationFrame, callback);
      Debouncer.debounce(job1, animationFrame, callback);
      var job2 = Debouncer.debounce(null, animationFrame, callback);
      Debouncer.debounce(job2, animationFrame, callback);

      requestAnimationFrame(function() {
        assert.isTrue(callback.calledTwice, 'callback should be called twice');
        done();
      });
    });

    test('same debouncer and multiple idle callbacks', function(done) {
      var callback = sinon.spy();
      var job = Debouncer.debounce(null, idlePeriod, callback);
      Debouncer.debounce(job, idlePeriod, callback);
      idlePeriod.run(function() {
        assert.isTrue(callback.calledOnce, 'callback should be called once');
        done();
      });
    });

    test('different debouncer and multiple idle callbacks', function(done) {
      var callback = sinon.spy();
      var job1 = Debouncer.debounce(null, idlePeriod, callback);
      Debouncer.debounce(job1, idlePeriod, callback);
      var job2 = Debouncer.debounce(null, idlePeriod, callback);
      Debouncer.debounce(job2, idlePeriod, callback);
      idlePeriod.run(function() {
        assert.isTrue(callback.calledTwice, 'callback should be called twice');
        done();
      });
    });

  });

});
</script>
</body>
</html>
